/*
 Copyright (C) 2010-2013. 
 Ezequiel Palumbo, Calipsoide FOSS Foundation.
 
 This program is free software: you can redistribute it and/or modify
 it under the terms of the GNU Lesser General Public License as published by
 the Free Software Foundation, either version 3 of the License, or
 (at your option) any later version.

 This program is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 GNU Lesser General Public License for more details.

 You should have received a copy of the GNU Lesser General Public License
 along with this program.  If not, see http://www.gnu.org/licenses/. 
 
 Email: contacto@ehpalumbo.com.ar 
*/

package org.calipsoide.dbf2xml.dbf;

import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.calipsoide.dbf2xml.model.Field;
import org.calipsoide.dbf2xml.model.Record;

import com.linuxense.javadbf.DBFException;
import com.linuxense.javadbf.DBFReader;

/**
 * 
 * Default implementation for reading xBase DBF files.<br> 
 * Uses <b>JavaDBF</b> API in order to access and read the file.  
 * 
 * @since 1.0
 * @author ehpalumbo
 *
 */
public class DBFDataExtractor implements TableDataExtractor {

	private static Log LOGGER = LogFactory.getLog(DBFDataExtractor.class);
	
	private DBFReader dbfReader;
	private InputStream inputStream; 
	private List<String> fieldNames;
	private List<String> projectedFieldNames;
	private Set<Field> restrictions;
 	private Integer since;
 	private Integer maximum;
	
	public DBFDataExtractor(final InputStream inputStream) 
			throws IOException {
		super();
		this.fieldNames = new ArrayList<String>();
		this.restrictions = new HashSet<Field>();
		this.inputStream = inputStream;
		this.since = 0;
		try {
			this.dbfReader = new DBFReader(inputStream);
			final int fieldCount = getDbfReader().getFieldCount();
			getLogger().debug("Number of fields in DBF: " + fieldCount);
			for (int i = 0; i < fieldCount; i++) {
				getFieldNames().add(getDbfReader().getField(i).getName());
			}
			getLogger().debug("Field names in DBF: " + getFieldNames());
			this.maximum = getDbfReader().getRecordCount();
			project(getFieldNames());
		} catch (DBFException e) {
			final String message = "Error trying to create reader component.";
			getLogger().error(message, e);
			throw new IOException(message, e);
		}
	}

	public Integer getSince() {
		return since;
	}
	
	public DBFDataExtractor from(Integer since) {
		if (since != null) {
			this.since = since;
		}
		return this;
	}
	
	public Integer getMaximum() {
		return maximum;
	}
	
	public DBFDataExtractor limitTo(Integer maximum) {
		if (maximum != null) {
			this.maximum = maximum;
		}
		return this;
	} 
	
	protected InputStream getInputStream() {
		return inputStream;
	}
	
	protected DBFReader getDbfReader() {
		return dbfReader;
	}
	
	public List<String> getFieldNames() {
		return fieldNames;
	}
	
	public List<String> getProjections() {
		return projectedFieldNames;
	}
	
	public Set<Field> getRestrictions() {
		return restrictions;
	}
	
	public DBFDataExtractor project(List<String> fieldNames) {
		if (fieldNames != null)	{ 
			this.projectedFieldNames = fieldNames;
		}
		return this;
	}
	
	public DBFDataExtractor restrict(Set<Field> restrictions) {
		if (restrictions != null) {
			this.restrictions = restrictions;
		}
		return this;
	}
	
	protected Log getLogger() {
		return LOGGER;
	}
	
	public List<Record> extract() {
		final List<Record> records = new ArrayList<Record>();
		try {
			rollToFirst();
			Object[] values = null;
			Record record = null;
			getLogger().debug("Projecting fields: " + getProjections());
			getLogger().debug("Restricting to: " + getRestrictions());
			int read = 0;
			int taken = 0;
			while (records.size() < getMaximum() &&
					((values = getDbfReader().nextRecord()) != null)) {
				final int recordId = getSince() + read++;
				getLogger().trace(
						"Reading record " + recordId + " from DBF...");
				record = new Record(recordId);
				final ArrayList<Field> fields = createFields(values);
				if (!getRestrictions().isEmpty()) {
					boolean included = true;
					for (Field restriction : getRestrictions()) {
						if (!fields.contains(restriction)) {
							included = false;
							break;
						}
					}
					if (!included) continue;
				}
				for (Field field : fields) {
					if (getProjections().contains(field.getName())) {
						record.getFields().add(field);
					}
				}
				records.add(record);
				taken++;
			}
			getLogger().debug(taken + " records were retrieved from DBF.");
		} catch (DBFException e) {
			final String message = "Error trying to read DBF data.";
			getLogger().error(message, e);
			throw new IllegalStateException(message, e);
		}
		return records;
	}

	private ArrayList<Field> createFields(Object[] values) {
		final ArrayList<Field> fields = new ArrayList<Field>(); 
		for (int j = 0; j < values.length; j++) {
			fields.add(new Field(getFieldNames().get(j), values[j]));
		}
		return fields;
	}

	protected void rollToFirst() throws DBFException {
		final Integer since = getSince();
		getLogger().debug("Rolling to record " + since + "...");
		final int recordCount = getDbfReader().getRecordCount();
		int until = (recordCount > since ? since : recordCount);
		for (int i = 0; i < until; i++) {
			getDbfReader().nextRecord();
		}
	}

}
